import datetime
import json
import mimetypes
import os

from flask import Flask, Response, render_template
from flask import jsonify, request

from env_config import SF, SimType, ENV
from nacos_service import NacosService
from util.log_s import LogSS
from web.log_config import config_log, config_web_log

app = Flask(__name__)
# 配置日志
config_log(app)
config_web_log(app)

# 启动Web项目时，设置Logger
LogSS().set_logger(app.logger)

# 获取环境变量
env = os.environ.get(SF.KEY_ENV)
if env:
    env = SimType(env)

# 初始化Nacos
ns = NacosService(app)


@app.before_request
def log_request_info():
    if (request.path.startswith('/pc') or request.path.startswith('/yc') or request.path.startswith(
            '/pm') or request.path.startswith('/tw')):
        app.web_logger.debug('[-->] Url: %s', request.url)
        app.web_logger.debug('[-->] Headers: %s', request.headers)
        app.web_logger.debug('[-->] Body: %s', json.dumps(request.get_json(), ensure_ascii=False))
    elif request.path.startswith('/info'):
        app.web_logger.debug('[-->] [PING]: %s', request.url)


@app.after_request
def after_request(response):
    if (request.path.startswith('/pc') or request.path.startswith('/yc') or request.path.startswith(
            '/pm') or request.path.startswith('/tw')):
        resp_data = response.data.decode() if response.data else ''
        app.web_logger.info('[<--] %s %s %s %s Response: %s %s', request.remote_addr, request.method, request.scheme,
                            request.full_path, response.status, resp_data)
    elif request.path.startswith('/info'):
        app.web_logger.debug('[<--] [PONE]: %s', response.status)

    # 回写 x-server
    if response.is_json:
        resp_j = response.get_json()
        resp_j['x-server'] = ns.host_info()
        response.data = json.dumps(resp_j)
    response.headers['x-server'] = ns.host_info()
    return response


def current_version() -> str | None:
    version: str | None = None
    try:
        with open('.version', 'r') as vf:
            version = vf.readline()
            if version:
                version = version.strip()
    except BaseException as ex:
        app.web_logger.warning('[Version] err: %s' % ex)
    return version


@app.get('/')
def index():
    """
    :return:
    """
    data = {'version': current_version(), 'env_gpu': ENV.gpu, 'env_db': SF, 'env': env}
    return render_template('index.html', data=data)


@app.get('/info')
def info():
    """
    系统信息
    ---
    tags:
    - 系统
    responses:
      200:
        description: OK
      500:
        description: ERROR
    :return:
    """
    version: str | None = current_version()
    if version:
        return jsonify({'code': 200, 'msg': 'ok', 'data': {'version': version}})
    else:
        return jsonify({'code': 500, 'msg': 'no version found.', 'data': '请检查项目根目录是否存在[.version]文件'})


def yc_start():
    """
    预测算法 - 启动
    ---
    tags:
    - 启动
    responses:
      200:
        description: 预测启动
    :return:
    """
    # 启动预测服务
    from test_service import YcServiceManager

    manager: YcServiceManager = YcServiceManager()
    if manager.task_id:
        return jsonify({'code': 400, 'msg': f'当前有正在执行的任务, taskId: {manager.task_id}'})
    predict_date = datetime.datetime.now().strftime("%Y-%m-%d")
    manager.run(app, None, predict_date)
    return jsonify({'code': 200, 'msg': 'ok', 'data': {'taskId': manager.task_id}})


def pm_start():
    """
    排模算法 启动
    ---
    tags:
    - 启动
    parameters:
      - name: body
        in: body
        required: false
        type: object
        schema:
          id: pm
          type: object
          properties:
            zoneList:
              type: array
              description: 巷道列表(默认全部巷道)
              items:
                type: string
              example:
                - "201"
                - "203-J"
    responses:
      200:
        description: 排摸启动
    :return:
    """
    from test_service import PmServiceManager

    manager: PmServiceManager = PmServiceManager()
    if manager.task_id:
        return jsonify({'code': 400, 'msg': f'当前有正在执行的任务, taskId: {manager.task_id}'})
    # 执行任务
    zone_list = []
    # 兼容非JSON调用
    if request.data:
        rjson = request.json
        zone_list = rjson.get('zoneList') if rjson and 'zoneList' in rjson else []
    data = {'zone_list': zone_list}
    manager.run(app, data)
    return jsonify({'code': 200, 'msg': 'ok', 'data': {'taskId': manager.task_id}})


def pm_params_export():
    """
    排模算法 入参导出
    ---
    tags:
    - 入参导出
    responses:
      200:
        description: 排摸启动
    :return:
    :return:
    """
    from task_service.x_pm_task import XPMTaskService
    from task_service import XpmContext

    pm_service = XPMTaskService()
    pm_service.context = XpmContext()
    pm_service.context.app = app
    # call private method
    pm_service._pre_process()
    # Resp
    resp = Response(f'params_list = {pm_service.params_list}'.encode())
    resp.headers['Content-Type'] = mimetypes.guess_type('xxx.py')[0]
    date_str = datetime.datetime.now().strftime('%Y%m%d_%H%M')
    filename = f'pm.mock.{date_str}.py'
    resp.headers['Content-Disposition'] = 'attachment; filename={}'.format(filename)
    return resp


def algorithm_input():
    """
    时间窗算法 入参导出
    ---
    tags:
    - 入参导出
    responses:
      200:
        description: OK
    :return:
    """
    # 从环境变量获取算法入参
    input_content = os.environ.get('twAlgorithmInput')
    if not input_content:
        return jsonify({'code': 500, 'msg': '未获取到算法入参'})
    # Resp
    resp = Response(input_content)
    resp.headers['Content-Type'] = mimetypes.guess_type('xxx.txt')[0]
    date_str = datetime.datetime.now().strftime('%Y%m%d_%H%M')
    filename = f'tw.input.{date_str}.txt'
    resp.headers['Content-Disposition'] = 'attachment; filename={}'.format(filename)
    return resp


def tw_start():
    """
    时间窗算法启动
    ---
    tags:
    - 启动
    parameters:
      - name: body
        in: body
        required: true
        schema:
          type: object
          properties:
            lpnListByOrder:
              type: array
              example:
                - {}
                - {}
            timeWindowList:
              type: array
              example:
                - {}
                - {}
            timeWindowConfigList:
              type: array
              example:
                - {}
                - {}
    responses:
      200:
        description: 预测启动
    :return:
    """
    from test_service import TwServiceManager
    from task_service import XtwContext

    j_data = request.json
    if not j_data:
        return jsonify({'code': 400, 'msg': 'no data'})
    manager: TwServiceManager = TwServiceManager()
    if manager.task_id:
        return jsonify({'code': 400, 'msg': f'当前有正在执行的任务, taskId: {manager.task_id}'})
    ctx: XtwContext = manager.run(app, j_data)
    if ctx.response_message:
        return jsonify({'code': 500, 'msg': ctx.response_message})
    if ctx.exception:
        return jsonify({'code': 500, 'msg': str(ctx.exception)})

    return jsonify({'code': 200, 'msg': 'ok', 'data': ctx.result})


def pc_start():
    """
    排程算法启动
    ---
    tags:
    - 启动
    parameters:
      - name: body
        in: body
        required: true
        schema:
          id: pc
          type: object
          properties:
            allEquInfo:
              type: array
              description: 所有设备数据
              items:
                type: object
                properties:
                  equCode:
                    type: string
                    description: 设备编号
                    example: "CN3203"
                  equType:
                    type: string
                    description: 设备类型
                    example: "301"
                  loc:
                    type: string
                    description: 设备位置
                    example: "CM3203"
                  status:
                    type: string
                    description: 设备状态 (1可用 2不可用)
                    example: "2"
                  workType:
                    type: string
                    description: 工作类型
                    example: "3001"
            deliveryTaskIdList:
              type: array
              description: 发货任务
              items:
                type: string
              example:
                - "1"
                - "2"
            interiorTsakIdList:
              type: array
              description: 在库任务
              items:
                type: string
            preworkTsakIdList:
              type: array
              description: 预作业任务
              items:
                type: string
            tallyTsakIdList:
              type: array
              description: 理货任务
              items:
                type: string
            pendingTaskIdList:
              type: array
              description: 锁定任务
              items:
                type: string
            baseDateTime:
              type: string
              description: 基准时间
              example: "2024-03-05T09:51:29"
    responses:
      200:
        description: 排程启动
    :return:
    """
    from test_service import PcServiceManager
    from task_service import XpcContext

    manager: PcServiceManager = PcServiceManager()
    if manager.task_id:
        return jsonify({'code': 400, 'msg': f'当前有正在执行的任务, taskId: {manager.task_id}'})
    # 参数获取及校验
    data = {}
    for k in ('deliveryTaskIdList', 'interiorTsakIdList', 'preworkTsakIdList', 'tallyTsakIdList', 'pendingTaskIdList',
              'allEquInfo', 'baseDateTime', 'workStationInfo', 'storerList'):
        data[k] = request.json.get(k)
    # 执行
    ctx: XpcContext = manager.run(app, data)
    if ctx.exception:
        return jsonify({'code': 500, 'msg': str(ctx.exception)})
    return jsonify({'code': 200, 'msg': 'ok', 'data': {'result': ctx.result}})


def log_stream():
    """
    系统日志
    ---
    tags:
    - 系统
    responses:
      200:
        description: OK
      500:
        description: ERROR
    :return:
    """
    from web.log_config import gen_log_file

    logfile = gen_log_file()
    with open(logfile) as f:
        return app.response_class(f.readlines(), mimetype='text/plain')


def directory():
    """
    获取 日志目录文件
    XXX: 配合 directory_display 使用，暂时仅支持当前目录文件
    :return:
    """
    home = os.environ.get('HOME_EXT')
    if not home:
        home = os.environ.get('HOME')
    logdir = os.path.join(home, 'log')
    files = os.listdir(logdir)

    def size(filename) -> float:
        return round(os.path.getsize(os.path.join(logdir, filename)) / 1024 / 1024, 2)

    files = [(_, size(_)) for _ in files if '.log' in _]
    return render_template('directory.html', files=files)


def directory_display(filename):
    """
    日志目录文件查看
    :param filename:
    :return:
    """
    home = os.environ.get('HOME_EXT')
    if not home:
        home = os.environ.get('HOME')
    filepath = os.path.join(home, 'log', filename)
    with open(filepath) as log:
        fix_lines = []
        for l in log.readlines():
            if l.startswith('['):
                fix_lines.append(l)
            else:
                if fix_lines:
                    fix_lines[-1] += l
                else:
                    fix_lines.append(l)
        data = {
            'logs': fix_lines
        }
        return render_template('directory_detail.html', data=data)


# 预测
if env in (SimType.LOCAL, SimType.ALL, SimType.YC):
    app.add_url_rule('/yc/startCal', view_func=yc_start, methods=('POST',))
# 排模
if env in (SimType.LOCAL, SimType.ALL, SimType.PM):
    app.add_url_rule('/pm/startCal', view_func=pm_start, methods=('POST',))

# 时间窗
if env in (SimType.LOCAL, SimType.ALL, SimType.TW):
    app.add_url_rule('/tw/startCal', view_func=tw_start, methods=('POST',))

# 排程
if env in (SimType.LOCAL, SimType.ALL, SimType.PC):
    app.add_url_rule('/pc/startCal', view_func=pc_start, methods=('POST',))

if env in (SimType.LOCAL, SimType.ALL, SimType.PC, SimType.PM, SimType.YC, SimType.TW):
    # 当前日志查询
    app.add_url_rule('/logs/stream', view_func=log_stream)
    # 通用接口
    app.add_url_rule('/files/directory', view_func=directory)
    app.add_url_rule('/files/directory_display/<filename>', view_func=directory_display)

# 额外接口
if env in (SimType.LOCAL, SimType.ALL, SimType.PC):
    # 参数导出
    app.add_url_rule('/export/pm/rawParam', view_func=pm_params_export)
    app.add_url_rule('/export/tw/algorithm_input', view_func=algorithm_input)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=40100)
